<?php

/**
 * Maikuro Framework
 *
 * LICENSE:
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * The license is bundled with this package in the file LICENSE.txt.
 * You may also obtain a copy of the License at this URL:
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * @copyright  Copyright (c) 2010, Jonathan Hedrén
 * @author     Jonathan Hedrén <jonathan.hedren@gmail.com>
 * @license    http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
 */

require_once 'IFormDecorator.php';
require_once 'html/HTMLDivElement.php';
require_once 'html/HTMLInputElement.php';
require_once 'html/HTMLLabelElement.php';
require_once 'html/HTMLSelectElement.php';
require_once 'html/HTMLOptionElement.php';
require_once 'html/HTMLTextareaElement.php';

class HTMLFormDecorator implements IFormDecorator
{

    protected $elementGroupIdPrefix = 'form-element-group-';

    protected $elementIdPrefix = 'form-element-';

    protected $elementWrapElementClass = 'HTMLDivElement';

    protected function createLabel(AbstractFormElement $element)
    {
        $label = new HTMLLabelElement();
        $label->for = $this->elementIdPrefix . $this->replaceBracketsForId($element->getName());
        $label->innerHTML = $element->getLabel();
        return $label;
    }

    public function decorateFormElement(AbstractFormElement $element,
        AbstractFormElement $wrapElement = null,
        $doWrap = true)
    {
        if ($doWrap && $wrapElement == null) {
            $wrapElementClass = $this->elementWrapElementClass;
            $wrapElement = new $wrapElementClass();
        }
        if ($element instanceof CheckboxFormElement) {
            return (string) $this->decorateCheckboxElement($element, $wrapElement);
        } else if ($element instanceof FileFormElement) {
            return (string) $this->decorateFileElement($element, $wrapElement);
        } else if ($element instanceof HiddenFormElement) {
            return (string) $this->decorateHiddenElement($element, $wrapElement);
        } else if ($element instanceof PasswordFormElement) {
            return (string) $this->decoratePasswordElement($element, $wrapElement);
        } else if ($element instanceof RadioFormElement) {
            return (string) $this->decorateRadioElement($element, $wrapElement);
        } else if ($element instanceof SelectFormElement) {
            return (string) $this->decorateSelectElement($element, $wrapElement);
        } else if ($element instanceof SubmitFormElement) {
            return (string) $this->decorateSubmitElement($element, $wrapElement);
        } else if ($element instanceof TextFormElement) {
            return (string) $this->decorateTextElement($element, $wrapElement);
        } else if ($element instanceof TextareaFormElement) {
            return (string) $this->decorateTextareaElement($element, $wrapElement);
        }
    }

    protected function decorateCheckboxElement(CheckboxFormElement $element,
        AbstractHTMLElement $wrapElement)
    {

        $shadow = new HTMLInputElement();
        $shadow->type = 'hidden';
        $shadow->name = $element->getName();
        $shadow->value = "0";

        $input = new HTMLInputElement();
        $input->type = 'checkbox';
        $input->name = $element->getName();
        $input->id = $this->elementIdPrefix . $this->replaceBracketsForId($element->getName());
        if ($element->isChecked()) {
            $input->checked = 'checked';
        }
        return $this->wrap($shadow . $input . $this->createLabel($element), $wrapElement, 'form-element checkbox');
    }

    protected function decorateFileElement(FileFormElement $element,
        AbstractHTMLElement $wrapElement)
    {
        $input = new HTMLInputElement();
        $input->type = 'file';
        $input->name = $element->getName();
        $input->value = htmlspecialchars($element->getValue());
        $input->id = $this->elementIdPrefix . $this->replaceBracketsForId($element->getName());
        return $this->wrap($this->createLabel($element) . $this->renderValidationMessages($element) . $input, $wrapElement, 'form-element file');
    }
    
    protected function decorateHiddenElement(HiddenFormElement $element,
        AbstractHTMLElement $wrapElement)
    {
        $input = new HTMLInputElement();
        $input->type = 'hidden';
        $input->name = $element->getName();
        $input->value = htmlspecialchars($element->getValue());
        $input->id = $this->elementIdPrefix . $this->replaceBracketsForId($element->getName());
        return $this->wrap($input, $wrapElement, 'form-element hidden');
    }

    protected function decoratePasswordElement(PasswordFormElement $element,
        AbstractHTMLElement $wrapElement)
    {
        $input = new HTMLInputElement();
        $input->type = 'password';
        $input->name = $element->getName();
        $input->value = htmlspecialchars($element->getValue());
        $input->id = $this->elementIdPrefix . $this->replaceBracketsForId($element->getName());
        return $this->wrap($this->createLabel($element) . $this->renderValidationMessages($element) . $input, $wrapElement, 'form-element password');
    }

    protected function decorateRadioElement(RadioFormElement $element,
        AbstractHTMLElement $wrapElement)
    {
        $output = '';
        foreach ($element->getOptions() as $label => $option) {
            $output .= '<label for="">' . $label . '</label>'
                     . '<input type="radio" name="' . $element->getName() . '" '
                     . 'value="' . $option . '"';
            if ($element->getSelectedOption() == $option) {
                $output .= ' checked="checked"';
            }
            $output .= ' />';
        }

        return $this->wrap($output, $wrapElement, 'form-element radio');
    }

    protected function decorateSelectElement(SelectFormElement $element,
        AbstractHTMLElement $wrapElement)
    {
        $select = new HTMLSelectElement();
        $select->name = $element->getName();
        $select->id = $this->elementIdPrefix . $this->replaceBracketsForId($element->getName());
        $selectInnerHTML = '';
        foreach ($element->getOptions() as $option) {
            $HTMLoption = new HTMLOptionElement();
            $HTMLoption->value = $option->value;
            $HTMLoption->text = $option->text;
            if ($option->disabled) {
                $HTMLoption->disabled = 'disabled';
            }
            if ($element->getValue() == $option->value) {
                $HTMLoption->selected = 'selected';
            }
            $selectInnerHTML .= $HTMLoption;
        }
        $select->innerHTML = $selectInnerHTML;
        return $this->wrap($this->createLabel($element) . $this->renderValidationMessages($element) . $select, $wrapElement, 'form-element select');
    }

    protected function decorateSubmitElement(SubmitFormElement $element,
        AbstractHTMLElement $wrapElement)
    {
        $input = new HTMLInputElement();
        $input->type = 'submit';
        $input->name = $element->getName();
        $input->value = $element->getLabel();
        return $this->wrap($input, $wrapElement);
    }

    protected function decorateTextElement(TextFormElement $element,
        AbstractHTMLElement $wrapElement)
    {
        $input = new HTMLInputElement();
        $input->type = 'text';
        $input->name = $element->getName();
        $input->value = htmlspecialchars($element->getValue());
        $input->id = $this->elementIdPrefix . $this->replaceBracketsForId($element->getName());
        return $this->wrap($this->createLabel($element) . $this->renderValidationMessages($element) . $input, $wrapElement, 'form-element text');
    }

    public function decorateTextareaElement(TextareaFormElement $element,
        AbstractHTMLElement $wrapElement)
    {
        $input = new HTMLTextareaElement();
        $input->name = $element->getName();
        $input->text = htmlspecialchars($element->getValue());
        $input->id = $this->elementIdPrefix . $this->replaceBracketsForId($element->getName());
        return $this->wrap($this->createLabel($element) . $this->renderValidationMessages($element) . $input, $wrapElement, 'form-element textarea');
    }

    public function decorateForm(Form $form)
    {
        $output = '<form method="' . $form->getMethod() . '" '
                . 'action="' . $form->getAction() . '">';

        $elements = $form->getElements();
        foreach ($elements as $element) {
            $output .= $this->decorateElement($element);
        }
        $output .= '</form>';
        return $output;
    }

    /**
     *
     * @param FormElementGroup $formElementGroup
     * @return string
     */
    public function decorateFormElementGroup(FormElementGroup $formElementGroup)
    {
        $wrapElement = new HTMLDivElement('form-group-element', $this->elementGroupIdPrefix . $formElementGroup->getName());

        foreach ($formElementGroup->getElements() as $element) {
            $wrapElement->innerHTML .= $element->toString();
        }
        return $wrapElement;
    }

    /**
     *
     * @param AbstractFormElement $element
     * @return HTMLUlElement
     */
    protected function renderValidationMessages(AbstractFormElement $element)
    {
        $vMessages = $element->getValidationMessages();
        if ($vMessages == null) {
            return;
        }
        $html = '';
        foreach ($vMessages as $vMessage) {
            $html .= '<span class="validation-error">' . Translator::translate($vMessage) . '</span>';
        }
        return $html;
    }

    protected function replaceBracketsForId($id)
    {
        return preg_replace('/\[(.*)\]/', '-$1', $id);
    }

    public function setElementIdPrefix($elementIdPrefix)
    {
        $this->elementIdPrefix = $elementIdPrefix;
        return $this;
    }

    public function setElementWrapElementClass($elementWrapElementClass)
    {
        $this->elementWrapElementClass = $elementWrapElementClass;
        return $this;
    }

    protected function wrap($output, AbstractHTMLElement $wrapElement,
        $wrapClass = null)
    {
        if ($wrapElement == null) {
            return $output;
        }

        $wrapElement->class = $wrapClass;

        if ($wrapElement->isWrappable()) {
            $wrapElement->innerHTML = $output;
            return $wrapElement;
        }
        return $output . $wrapElement;
    }
}
